// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/shell/renderer/layout_test/blink_test_runner.h"

#include <stddef.h>

#include <algorithm>
#include <clocale>
#include <cmath>
#include <memory>
#include <utility>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/debug/debugger.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/md5.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/plugins/renderer/plugin_placeholder.h"
#include "components/test_runner/app_banner_service.h"
#include "components/test_runner/gamepad_controller.h"
#include "components/test_runner/layout_and_paint_async_then.h"
#include "components/test_runner/pixel_dump.h"
#include "components/test_runner/web_test_interfaces.h"
#include "components/test_runner/web_test_runner.h"
#include "components/test_runner/web_view_test_proxy.h"
#include "content/common/content_switches_internal.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/web_preferences.h"
#include "content/public/renderer/media_stream_utils.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_visitor.h"
#include "content/public/renderer/renderer_gamepad_provider.h"
#include "content/public/test/layouttest_support.h"
#include "content/shell/common/layout_test/layout_test_messages.h"
#include "content/shell/common/shell_messages.h"
#include "content/shell/common/shell_switches.h"
#include "content/shell/renderer/layout_test/blink_test_helpers.h"
#include "content/shell/renderer/layout_test/layout_test_render_thread_observer.h"
#include "content/shell/renderer/layout_test/leak_detector.h"
#include "media/base/audio_capturer_source.h"
#include "media/base/audio_parameters.h"
#include "media/capture/video_capturer_source.h"
#include "media/media_features.h"
#include "net/base/filename_util.h"
#include "net/base/net_errors.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "services/service_manager/public/cpp/interface_registry.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/public/platform/FilePathConversion.h"
#include "third_party/WebKit/public/platform/Platform.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "third_party/WebKit/public/platform/WebPoint.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebThread.h"
#include "third_party/WebKit/public/platform/WebTraceLocation.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebURLError.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/platform/WebURLResponse.h"
#include "third_party/WebKit/public/platform/modules/app_banner/app_banner.mojom.h"
#include "third_party/WebKit/public/web/WebArrayBufferView.h"
#include "third_party/WebKit/public/web/WebContextMenuData.h"
#include "third_party/WebKit/public/web/WebDataSource.h"
#include "third_party/WebKit/public/web/WebDevToolsAgent.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebFrameWidget.h"
#include "third_party/WebKit/public/web/WebHistoryItem.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebLeakDetector.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebTestingSupport.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/icc_profile.h"

using blink::Platform;
using blink::WebArrayBufferView;
using blink::WebContextMenuData;
using blink::WebDeviceMotionData;
using blink::WebDeviceOrientationData;
using blink::WebDevToolsAgent;
using blink::WebElement;
using blink::WebFrame;
using blink::WebHistoryItem;
using blink::WebLocalFrame;
using blink::WebPoint;
using blink::WebRect;
using blink::WebScriptSource;
using blink::WebSize;
using blink::WebString;
using blink::WebTestingSupport;
using blink::WebThread;
using blink::WebTraceLocation;
using blink::WebURL;
using blink::WebURLError;
using blink::WebURLRequest;
using blink::WebVector;
using blink::WebView;

namespace content {

namespace {

    class SyncNavigationStateVisitor : public RenderViewVisitor {
    public:
        SyncNavigationStateVisitor() { }
        ~SyncNavigationStateVisitor() override { }

        bool Visit(RenderView* render_view) override
        {
            SyncNavigationState(render_view);
            return true;
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(SyncNavigationStateVisitor);
    };

    class UseSynchronousResizeModeVisitor : public RenderViewVisitor {
    public:
        explicit UseSynchronousResizeModeVisitor(bool enable)
            : enable_(enable)
        {
        }
        ~UseSynchronousResizeModeVisitor() override { }

        bool Visit(RenderView* render_view) override
        {
            UseSynchronousResizeMode(render_view, enable_);
            return true;
        }

    private:
        bool enable_;
    };

    class MockGamepadProvider : public RendererGamepadProvider {
    public:
        explicit MockGamepadProvider(test_runner::GamepadController* controller)
            : RendererGamepadProvider(nullptr)
            , controller_(controller)
        {
        }
        ~MockGamepadProvider() override
        {
            StopIfObserving();
        }

        // RendererGamepadProvider implementation.
        void SampleGamepads(blink::WebGamepads& gamepads) override
        {
            controller_->SampleGamepads(gamepads);
        }
        void Start(blink::WebPlatformEventListener* listener) override
        {
            controller_->SetListener(static_cast<blink::WebGamepadListener*>(listener));
            RendererGamepadProvider::Start(listener);
        }
        void SendStartMessage() override { }
        void SendStopMessage() override { }

    private:
        std::unique_ptr<test_runner::GamepadController> controller_;

        DISALLOW_COPY_AND_ASSIGN(MockGamepadProvider);
    };

    class MockVideoCapturerSource : public media::VideoCapturerSource {
    public:
        MockVideoCapturerSource() = default;
        ~MockVideoCapturerSource() override { }

        void GetCurrentSupportedFormats(
            int max_requested_width,
            int max_requested_height,
            double max_requested_frame_rate,
            const VideoCaptureDeviceFormatsCB& callback) override
        {
            const int supported_width = 640;
            const int supported_height = 480;
            const float supported_framerate = 60.0;
            callback.Run(media::VideoCaptureFormats(
                1, media::VideoCaptureFormat(gfx::Size(supported_width, supported_height), supported_framerate, media::PIXEL_FORMAT_I420)));
        }
        void StartCapture(const media::VideoCaptureParams& params,
            const VideoCaptureDeliverFrameCB& new_frame_callback,
            const RunningCallback& running_callback) override
        {
            running_callback.Run(true);
        }
        void StopCapture() override { }
    };

    class MockAudioCapturerSource : public media::AudioCapturerSource {
    public:
        MockAudioCapturerSource() = default;

        void Initialize(const media::AudioParameters& params,
            CaptureCallback* callback,
            int session_id) override { }
        void Start() override { }
        void Stop() override { }
        void SetVolume(double volume) override { }
        void SetAutomaticGainControl(bool enable) override { }

    protected:
        ~MockAudioCapturerSource() override { }
    };

    // Tests in web-platform-tests use absolute path links such as
    //   <script src="/resources/testharness.js">.
    // Because we load the tests as local files, such links don't work.
    // This function fixes this issue by rewriting file: URLs which were produced
    // from such links so that they point actual files in wpt/.
    WebURL RewriteAbsolutePathInWPT(const std::string& utf8_url)
    {
        const char kFileScheme[] = "file:///";
        const int kFileSchemeLen = arraysize(kFileScheme) - 1;
        if (utf8_url.compare(0, kFileSchemeLen, kFileScheme, kFileSchemeLen) != 0)
            return WebURL();
        if (utf8_url.find("/LayoutTests/") != std::string::npos)
            return WebURL();
#if defined(OS_WIN)
        // +3 for a drive letter, :, and /.
        const int kFileSchemeAndDriveLen = kFileSchemeLen + 3;
        if (utf8_url.size() <= kFileSchemeAndDriveLen)
            return WebURL();
        std::string path = utf8_url.substr(kFileSchemeAndDriveLen);
#else
        std::string path = utf8_url.substr(kFileSchemeLen);
#endif
        base::FilePath new_path = LayoutTestRenderThreadObserver::GetInstance()
                                      ->webkit_source_dir()
                                      .Append(FILE_PATH_LITERAL("LayoutTests/external/wpt/"))
                                      .AppendASCII(path);
        return WebURL(net::FilePathToFileURL(new_path));
    }

} // namespace

BlinkTestRunner::BlinkTestRunner(RenderView* render_view)
    : RenderViewObserver(render_view)
    , RenderViewObserverTracker<BlinkTestRunner>(render_view)
    , is_main_window_(false)
    , focus_on_next_commit_(false)
    , leak_detector_(new LeakDetector(this))
{
}

BlinkTestRunner::~BlinkTestRunner()
{
}

// WebTestDelegate  -----------------------------------------------------------

void BlinkTestRunner::ClearEditCommand()
{
    render_view()->ClearEditCommands();
}

void BlinkTestRunner::SetEditCommand(const std::string& name,
    const std::string& value)
{
    render_view()->SetEditCommandForNextKeyEvent(name, value);
}

void BlinkTestRunner::SetGamepadProvider(
    test_runner::GamepadController* controller)
{
    std::unique_ptr<MockGamepadProvider> provider(
        new MockGamepadProvider(controller));
    SetMockGamepadProvider(std::move(provider));
}

void BlinkTestRunner::SetDeviceLightData(const double data)
{
    SetMockDeviceLightData(data);
}

void BlinkTestRunner::SetDeviceMotionData(const WebDeviceMotionData& data)
{
    SetMockDeviceMotionData(data);
}

void BlinkTestRunner::SetDeviceOrientationData(
    const WebDeviceOrientationData& data)
{
    SetMockDeviceOrientationData(data);
}

void BlinkTestRunner::PrintMessage(const std::string& message)
{
    Send(new ShellViewHostMsg_PrintMessage(routing_id(), message));
}

void BlinkTestRunner::PostTask(const base::Closure& task)
{
    Platform::current()->currentThread()->getSingleThreadTaskRunner()->PostTask(
        FROM_HERE, task);
}

void BlinkTestRunner::PostDelayedTask(const base::Closure& task, long long ms)
{
    Platform::current()
        ->currentThread()
        ->getSingleThreadTaskRunner()
        ->PostDelayedTask(FROM_HERE, task, base::TimeDelta::FromMilliseconds(ms));
}

WebString BlinkTestRunner::RegisterIsolatedFileSystem(
    const blink::WebVector<blink::WebString>& absolute_filenames)
{
    std::vector<base::FilePath> files;
    for (size_t i = 0; i < absolute_filenames.size(); ++i)
        files.push_back(blink::WebStringToFilePath(absolute_filenames[i]));
    std::string filesystem_id;
    Send(new LayoutTestHostMsg_RegisterIsolatedFileSystem(
        routing_id(), files, &filesystem_id));
    return WebString::fromUTF8(filesystem_id);
}

long long BlinkTestRunner::GetCurrentTimeInMillisecond()
{
    return base::TimeDelta(base::Time::Now() - base::Time::UnixEpoch()).ToInternalValue() / base::Time::kMicrosecondsPerMillisecond;
}

WebString BlinkTestRunner::GetAbsoluteWebStringFromUTF8Path(
    const std::string& utf8_path)
{
    base::FilePath path = base::FilePath::FromUTF8Unsafe(utf8_path);
    if (!path.IsAbsolute()) {
        GURL base_url = net::FilePathToFileURL(test_config_.current_working_directory.Append(
            FILE_PATH_LITERAL("foo")));
        net::FileURLToFilePath(base_url.Resolve(utf8_path), &path);
    }
    return path.AsUTF16Unsafe();
}

WebURL BlinkTestRunner::LocalFileToDataURL(const WebURL& file_url)
{
    base::FilePath local_path;
    if (!net::FileURLToFilePath(file_url, &local_path))
        return WebURL();

    std::string contents;
    Send(new LayoutTestHostMsg_ReadFileToString(
        routing_id(), local_path, &contents));

    std::string contents_base64;
    base::Base64Encode(contents, &contents_base64);

    const char data_url_prefix[] = "data:text/css:charset=utf-8;base64,";
    return WebURL(GURL(data_url_prefix + contents_base64));
}

WebURL BlinkTestRunner::RewriteLayoutTestsURL(const std::string& utf8_url,
    bool is_wpt_mode)
{
    if (is_wpt_mode) {
        WebURL rewritten_url = RewriteAbsolutePathInWPT(utf8_url);
        if (!rewritten_url.isEmpty())
            return rewritten_url;
        return WebURL(GURL(utf8_url));
    }

    const char kPrefix[] = "file:///tmp/LayoutTests/";
    const int kPrefixLen = arraysize(kPrefix) - 1;

    if (utf8_url.compare(0, kPrefixLen, kPrefix, kPrefixLen))
        return WebURL(GURL(utf8_url));

    base::FilePath replace_path = LayoutTestRenderThreadObserver::GetInstance()->webkit_source_dir().Append(FILE_PATH_LITERAL("LayoutTests/"));
#if defined(OS_WIN)
    std::string utf8_path = base::WideToUTF8(replace_path.value());
#else
    std::string utf8_path = base::WideToUTF8(base::SysNativeMBToWide(replace_path.value()));
#endif
    std::string new_url = std::string("file://") + utf8_path + utf8_url.substr(kPrefixLen);
    return WebURL(GURL(new_url));
}

test_runner::TestPreferences* BlinkTestRunner::Preferences()
{
    return &prefs_;
}

void BlinkTestRunner::ApplyPreferences()
{
    WebPreferences prefs = render_view()->GetWebkitPreferences();
    ExportLayoutTestSpecificPreferences(prefs_, &prefs);
    render_view()->SetWebkitPreferences(prefs);
    Send(new ShellViewHostMsg_OverridePreferences(routing_id(), prefs));
}

std::string BlinkTestRunner::makeURLErrorDescription(const WebURLError& error)
{
    std::string domain = error.domain.utf8();
    int code = error.reason;

    if (domain == net::kErrorDomain) {
        domain = "NSURLErrorDomain";
        switch (error.reason) {
        case net::ERR_ABORTED:
            code = -999; // NSURLErrorCancelled
            break;
        case net::ERR_UNSAFE_PORT:
            // Our unsafe port checking happens at the network stack level, but we
            // make this translation here to match the behavior of stock WebKit.
            domain = "WebKitErrorDomain";
            code = 103;
            break;
        case net::ERR_ADDRESS_INVALID:
        case net::ERR_ADDRESS_UNREACHABLE:
        case net::ERR_NETWORK_ACCESS_DENIED:
            code = -1004; // NSURLErrorCannotConnectToHost
            break;
        }
    } else {
        DLOG(WARNING) << "Unknown error domain";
    }

    return base::StringPrintf("<NSError domain %s, code %d, failing URL \"%s\">",
        domain.c_str(), code, error.unreachableURL.string().utf8().data());
}

void BlinkTestRunner::UseUnfortunateSynchronousResizeMode(bool enable)
{
    UseSynchronousResizeModeVisitor visitor(enable);
    RenderView::ForEach(&visitor);
}

void BlinkTestRunner::EnableAutoResizeMode(const WebSize& min_size,
    const WebSize& max_size)
{
    content::EnableAutoResizeMode(render_view(), min_size, max_size);
}

void BlinkTestRunner::DisableAutoResizeMode(const WebSize& new_size)
{
    content::DisableAutoResizeMode(render_view(), new_size);
    if (!new_size.isEmpty())
        ForceResizeRenderView(render_view(), new_size);
}

void BlinkTestRunner::ClearDevToolsLocalStorage()
{
    Send(new ShellViewHostMsg_ClearDevToolsLocalStorage(routing_id()));
}

void BlinkTestRunner::ShowDevTools(const std::string& settings,
    const std::string& frontend_url)
{
    Send(new ShellViewHostMsg_ShowDevTools(
        routing_id(), settings, frontend_url));
}

void BlinkTestRunner::CloseDevTools()
{
    Send(new ShellViewHostMsg_CloseDevTools(routing_id()));
    WebDevToolsAgent* agent = render_view()->GetMainRenderFrame()->GetWebFrame()->devToolsAgent();
    if (agent)
        agent->detach();
}

void BlinkTestRunner::EvaluateInWebInspector(int call_id,
    const std::string& script)
{
    Send(new ShellViewHostMsg_EvaluateInDevTools(
        routing_id(), call_id, script));
}

std::string BlinkTestRunner::EvaluateInWebInspectorOverlay(
    const std::string& script)
{
    WebDevToolsAgent* agent = render_view()->GetMainRenderFrame()->GetWebFrame()->devToolsAgent();
    if (!agent)
        return std::string();

    return agent->evaluateInWebInspectorOverlay(
                    WebString::fromUTF8(script))
        .utf8();
}

void BlinkTestRunner::ClearAllDatabases()
{
    Send(new LayoutTestHostMsg_ClearAllDatabases(routing_id()));
}

void BlinkTestRunner::SetDatabaseQuota(int quota)
{
    Send(new LayoutTestHostMsg_SetDatabaseQuota(routing_id(), quota));
}

void BlinkTestRunner::SimulateWebNotificationClick(
    const std::string& title,
    int action_index,
    const base::NullableString16& reply)
{
    Send(new LayoutTestHostMsg_SimulateWebNotificationClick(routing_id(), title,
        action_index, reply));
}

void BlinkTestRunner::SimulateWebNotificationClose(const std::string& title,
    bool by_user)
{
    Send(new LayoutTestHostMsg_SimulateWebNotificationClose(routing_id(), title,
        by_user));
}

void BlinkTestRunner::SetDeviceScaleFactor(float factor)
{
    content::SetDeviceScaleFactor(render_view(), factor);
}

float BlinkTestRunner::GetWindowToViewportScale()
{
    return content::GetWindowToViewportScale(render_view());
}

std::unique_ptr<blink::WebInputEvent>
BlinkTestRunner::TransformScreenToWidgetCoordinates(
    test_runner::WebWidgetTestProxyBase* web_widget_test_proxy_base,
    const blink::WebInputEvent& event)
{
    return content::TransformScreenToWidgetCoordinates(web_widget_test_proxy_base,
        event);
}

test_runner::WebWidgetTestProxyBase* BlinkTestRunner::GetWebWidgetTestProxyBase(
    blink::WebLocalFrame* frame)
{
    return content::GetWebWidgetTestProxyBase(frame);
}

void BlinkTestRunner::EnableUseZoomForDSF()
{
    base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kEnableUseZoomForDSF);
}

bool BlinkTestRunner::IsUseZoomForDSFEnabled()
{
    return content::IsUseZoomForDSFEnabled();
}

void BlinkTestRunner::SetDeviceColorProfile(const std::string& name)
{
    content::SetDeviceColorProfile(render_view(), GetTestingICCProfile(name));
}

void BlinkTestRunner::SetBluetoothFakeAdapter(const std::string& adapter_name,
    const base::Closure& callback)
{
    GetBluetoothFakeAdapterSetter().Set(adapter_name, callback);
}

void BlinkTestRunner::SetBluetoothManualChooser(bool enable)
{
    Send(new ShellViewHostMsg_SetBluetoothManualChooser(routing_id(), enable));
}

void BlinkTestRunner::GetBluetoothManualChooserEvents(
    const base::Callback<void(const std::vector<std::string>&)>& callback)
{
    get_bluetooth_events_callbacks_.push_back(callback);
    Send(new ShellViewHostMsg_GetBluetoothManualChooserEvents(routing_id()));
}

void BlinkTestRunner::SendBluetoothManualChooserEvent(
    const std::string& event,
    const std::string& argument)
{
    Send(new ShellViewHostMsg_SendBluetoothManualChooserEvent(routing_id(), event,
        argument));
}

void BlinkTestRunner::SetFocus(blink::WebView* web_view, bool focus)
{
    RenderView* render_view = RenderView::FromWebView(web_view);
    if (render_view) // Check whether |web_view| has been already closed.
        SetFocusAndActivate(render_view, focus);
}

void BlinkTestRunner::SetBlockThirdPartyCookies(bool block)
{
    Send(new LayoutTestHostMsg_BlockThirdPartyCookies(routing_id(), block));
}

std::string BlinkTestRunner::PathToLocalResource(const std::string& resource)
{
#if defined(OS_WIN)
    if (base::StartsWith(resource, "/tmp/", base::CompareCase::SENSITIVE)) {
        // We want a temp file.
        GURL base_url = net::FilePathToFileURL(test_config_.temp_path);
        return base_url.Resolve(resource.substr(sizeof("/tmp/") - 1)).spec();
    }
#endif

    // Some layout tests use file://// which we resolve as a UNC path. Normalize
    // them to just file:///.
    std::string result = resource;
    static const size_t kFileLen = sizeof("file:///") - 1;
    while (base::StartsWith(base::ToLowerASCII(result), "file:////",
        base::CompareCase::SENSITIVE)) {
        result = result.substr(0, kFileLen) + result.substr(kFileLen + 1);
    }
    return RewriteLayoutTestsURL(result, false /* is_wpt_mode */).string().utf8();
}

void BlinkTestRunner::SetLocale(const std::string& locale)
{
    setlocale(LC_ALL, locale.c_str());
}

void BlinkTestRunner::OnLayoutTestRuntimeFlagsChanged(
    const base::DictionaryValue& changed_values)
{
    // Ignore changes that happen before we got the initial, accumulated
    // layout flag changes in ShellViewMsg_ReplicateTestConfiguration.
    if (!is_main_window_)
        return;

    RenderThread::Get()->Send(
        new LayoutTestHostMsg_LayoutTestRuntimeFlagsChanged(changed_values));
}

void BlinkTestRunner::TestFinished()
{
    if (!is_main_window_ || !render_view()->GetMainRenderFrame()) {
        RenderThread::Get()->Send(
            new LayoutTestHostMsg_TestFinishedInSecondaryRenderer());
        return;
    }
    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();
    interfaces->SetTestIsRunning(false);
    if (interfaces->TestRunner()->ShouldDumpBackForwardList()) {
        SyncNavigationStateVisitor visitor;
        RenderView::ForEach(&visitor);
        Send(new ShellViewHostMsg_CaptureSessionHistory(routing_id()));
    } else {
        // clean out the lifecycle if needed before capturing the layout tree
        // dump and pixels from the compositor.
        render_view()->GetWebView()->mainFrame()->toWebLocalFrame()->frameWidget()->updateAllLifecyclePhases();
        CaptureDump();
    }
}

void BlinkTestRunner::CloseRemainingWindows()
{
    Send(new ShellViewHostMsg_CloseRemainingWindows(routing_id()));
}

void BlinkTestRunner::DeleteAllCookies()
{
    Send(new LayoutTestHostMsg_DeleteAllCookies(routing_id()));
}

int BlinkTestRunner::NavigationEntryCount()
{
    return GetLocalSessionHistoryLength(render_view());
}

void BlinkTestRunner::GoToOffset(int offset)
{
    Send(new ShellViewHostMsg_GoToOffset(routing_id(), offset));
}

void BlinkTestRunner::Reload()
{
    Send(new ShellViewHostMsg_Reload(routing_id()));
}

void BlinkTestRunner::LoadURLForFrame(const WebURL& url,
    const std::string& frame_name)
{
    Send(new ShellViewHostMsg_LoadURLForFrame(
        routing_id(), url, frame_name));
}

bool BlinkTestRunner::AllowExternalPages()
{
    return test_config_.allow_external_pages;
}

std::string BlinkTestRunner::DumpHistoryForWindow(blink::WebView* web_view)
{
    size_t pos = 0;
    std::vector<int>::iterator id;
    for (id = routing_ids_.begin(); id != routing_ids_.end(); ++id, ++pos) {
        RenderView* render_view = RenderView::FromRoutingID(*id);
        if (!render_view) {
            NOTREACHED();
            continue;
        }
        if (render_view->GetWebView() == web_view)
            break;
    }

    if (id == routing_ids_.end()) {
        NOTREACHED();
        return std::string();
    }
    return DumpBackForwardList(session_histories_[pos],
        current_entry_indexes_[pos]);
}

void BlinkTestRunner::FetchManifest(
    blink::WebView* view,
    const GURL& url,
    const base::Callback<void(const blink::WebURLResponse& response,
        const std::string& data)>& callback)
{
    ::content::FetchManifest(view, url, callback);
}

void BlinkTestRunner::SetPermission(const std::string& name,
    const std::string& value,
    const GURL& origin,
    const GURL& embedding_origin)
{
    blink::mojom::PermissionStatus status;
    if (value == "granted") {
        status = blink::mojom::PermissionStatus::GRANTED;
    } else if (value == "prompt") {
        status = blink::mojom::PermissionStatus::ASK;
    } else if (value == "denied") {
        status = blink::mojom::PermissionStatus::DENIED;
    } else {
        NOTREACHED();
        status = blink::mojom::PermissionStatus::DENIED;
    }

    Send(new LayoutTestHostMsg_SetPermission(
        routing_id(), name, status, origin, embedding_origin));
}

void BlinkTestRunner::ResetPermissions()
{
    Send(new LayoutTestHostMsg_ResetPermissions(routing_id()));
}

cc::SharedBitmapManager* BlinkTestRunner::GetSharedBitmapManager()
{
    return RenderThread::Get()->GetSharedBitmapManager();
}

void BlinkTestRunner::DispatchBeforeInstallPromptEvent(
    const std::vector<std::string>& event_platforms,
    const base::Callback<void(bool)>& callback)
{
    app_banner_service_.reset(new test_runner::AppBannerService());

    service_manager::InterfaceRegistry::TestApi test_api(
        render_view()->GetMainRenderFrame()->GetInterfaceRegistry());
    test_api.GetLocalInterface(
        mojo::MakeRequest(&app_banner_service_->controller()));

    app_banner_service_->SendBannerPromptRequest(event_platforms, callback);
}

void BlinkTestRunner::ResolveBeforeInstallPromptPromise(
    const std::string& platform)
{
    if (app_banner_service_) {
        app_banner_service_->ResolvePromise(platform);
        app_banner_service_.reset(nullptr);
    }
}

blink::WebPlugin* BlinkTestRunner::CreatePluginPlaceholder(
    blink::WebLocalFrame* frame, const blink::WebPluginParams& params)
{
    if (params.mimeType != "application/x-plugin-placeholder-test")
        return nullptr;

    plugins::PluginPlaceholder* placeholder = new plugins::PluginPlaceholder(render_view()->GetMainRenderFrame(), frame,
        params, "<div>Test content</div>");
    return placeholder->plugin();
}

float BlinkTestRunner::GetDeviceScaleFactor() const
{
    return render_view()->GetDeviceScaleFactor();
}

void BlinkTestRunner::RunIdleTasks(const base::Closure& callback)
{
    SchedulerRunIdleTasks(callback);
}

void BlinkTestRunner::ForceTextInputStateUpdate(WebFrame* frame)
{
    ForceTextInputStateUpdateForRenderFrame(RenderFrame::FromWebFrame(frame));
}

bool BlinkTestRunner::AddMediaStreamVideoSourceAndTrack(
    blink::WebMediaStream* stream)
{
    DCHECK(stream);
#if BUILDFLAG(ENABLE_WEBRTC)
    return AddVideoTrackToMediaStream(base::MakeUnique<MockVideoCapturerSource>(),
        false, // is_remote
        false, // is_readonly
        stream);
#else
    return false;
#endif
}

bool BlinkTestRunner::AddMediaStreamAudioSourceAndTrack(
    blink::WebMediaStream* stream)
{
    DCHECK(stream);
#if BUILDFLAG(ENABLE_WEBRTC)
    return AddAudioTrackToMediaStream(
        make_scoped_refptr(new MockAudioCapturerSource()),
        48000, // sample rate
        media::CHANNEL_LAYOUT_STEREO,
        480, // sample frames per buffer
        false, // is_remote
        false, // is_readonly
        stream);
#else
    return false;
#endif
}

// RenderViewObserver  --------------------------------------------------------

void BlinkTestRunner::DidClearWindowObject(WebLocalFrame* frame)
{
    WebTestingSupport::injectInternalsObject(frame);
}

bool BlinkTestRunner::OnMessageReceived(const IPC::Message& message)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(BlinkTestRunner, message)
    IPC_MESSAGE_HANDLER(ShellViewMsg_SessionHistory, OnSessionHistory)
    IPC_MESSAGE_HANDLER(ShellViewMsg_Reset, OnReset)
    IPC_MESSAGE_HANDLER(ShellViewMsg_TestFinishedInSecondaryRenderer,
        OnTestFinishedInSecondaryRenderer)
    IPC_MESSAGE_HANDLER(ShellViewMsg_TryLeakDetection, OnTryLeakDetection)
    IPC_MESSAGE_HANDLER(ShellViewMsg_ReplyBluetoothManualChooserEvents,
        OnReplyBluetoothManualChooserEvents)
    IPC_MESSAGE_HANDLER(ShellViewMsg_LayoutDumpCompleted, OnLayoutDumpCompleted)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    return handled;
}

void BlinkTestRunner::Navigate(const GURL& url)
{
    focus_on_next_commit_ = true;
}

void BlinkTestRunner::DidCommitProvisionalLoad(WebLocalFrame* frame,
    bool is_new_navigation)
{
    if (!focus_on_next_commit_)
        return;
    focus_on_next_commit_ = false;
    render_view()->GetWebView()->setFocusedFrame(frame);
}

void BlinkTestRunner::DidFailProvisionalLoad(WebLocalFrame* frame,
    const WebURLError& error)
{
    focus_on_next_commit_ = false;
}

// Public methods - -----------------------------------------------------------

void BlinkTestRunner::Reset(bool for_new_test)
{
    prefs_.Reset();
    routing_ids_.clear();
    session_histories_.clear();
    current_entry_indexes_.clear();

    render_view()->ClearEditCommands();
    if (for_new_test) {
        if (render_view()->GetWebView()->mainFrame()->isWebLocalFrame())
            render_view()->GetWebView()->mainFrame()->setName(WebString());
        render_view()->GetWebView()->mainFrame()->clearOpener();
    }

    // Resetting the internals object also overrides the WebPreferences, so we
    // have to sync them to WebKit again.
    if (render_view()->GetWebView()->mainFrame()->isWebLocalFrame()) {
        WebTestingSupport::resetInternalsObject(
            render_view()->GetWebView()->mainFrame()->toWebLocalFrame());
        render_view()->SetWebkitPreferences(render_view()->GetWebkitPreferences());
    }
}

// Private methods  -----------------------------------------------------------

void BlinkTestRunner::CaptureDump()
{
    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();
    TRACE_EVENT0("shell", "BlinkTestRunner::CaptureDump");

    if (interfaces->TestRunner()->ShouldDumpAsAudio()) {
        std::vector<unsigned char> vector_data;
        interfaces->TestRunner()->GetAudioData(&vector_data);
        Send(new ShellViewHostMsg_AudioDump(routing_id(), vector_data));
        CaptureDumpContinued();
        return;
    }

    std::string custom_text_dump;
    if (interfaces->TestRunner()->HasCustomTextDump(&custom_text_dump)) {
        Send(new ShellViewHostMsg_TextDump(routing_id(), custom_text_dump + "\n"));
        CaptureDumpContinued();
        return;
    }

    if (!interfaces->TestRunner()->IsRecursiveLayoutDumpRequested()) {
        std::string layout_dump = interfaces->TestRunner()->DumpLayout(
            render_view()->GetMainRenderFrame()->GetWebFrame());
        OnLayoutDumpCompleted(std::move(layout_dump));
        return;
    }

    Send(new ShellViewHostMsg_InitiateLayoutDump(routing_id()));
    // OnLayoutDumpCompleted will be eventually called by an IPC from the browser.
}

void BlinkTestRunner::OnLayoutDumpCompleted(std::string completed_layout_dump)
{
    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();
    if (interfaces->TestRunner()->ShouldDumpBackForwardList()) {
        for (WebView* web_view : interfaces->GetWindowList())
            completed_layout_dump.append(DumpHistoryForWindow(web_view));
    }

    Send(new ShellViewHostMsg_TextDump(routing_id(),
        std::move(completed_layout_dump)));

    CaptureDumpContinued();
}

void BlinkTestRunner::CaptureDumpContinued()
{
    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();
    if (test_config_.enable_pixel_dumping && interfaces->TestRunner()->ShouldGeneratePixelResults() && !interfaces->TestRunner()->ShouldDumpAsAudio()) {
        CHECK(render_view()->GetWebView()->isAcceleratedCompositingActive());
        interfaces->TestRunner()->DumpPixelsAsync(
            render_view()->GetWebView(),
            base::Bind(&BlinkTestRunner::OnPixelsDumpCompleted,
                base::Unretained(this)));
        return;
    }

    CaptureDumpComplete();
}

void BlinkTestRunner::OnPixelsDumpCompleted(const SkBitmap& snapshot)
{
    DCHECK_NE(0, snapshot.info().width());
    DCHECK_NE(0, snapshot.info().height());

    SkAutoLockPixels snapshot_lock(snapshot);
    // The snapshot arrives from the GPU process via shared memory. Because MSan
    // can't track initializedness across processes, we must assure it that the
    // pixels are in fact initialized.
    MSAN_UNPOISON(snapshot.getPixels(), snapshot.getSize());
    base::MD5Digest digest;
    base::MD5Sum(snapshot.getPixels(), snapshot.getSize(), &digest);
    std::string actual_pixel_hash = base::MD5DigestToBase16(digest);

    if (actual_pixel_hash == test_config_.expected_pixel_hash) {
        SkBitmap empty_image;
        Send(new ShellViewHostMsg_ImageDump(
            routing_id(), actual_pixel_hash, empty_image));
    } else {
        Send(new ShellViewHostMsg_ImageDump(
            routing_id(), actual_pixel_hash, snapshot));
    }

    CaptureDumpComplete();
}

void BlinkTestRunner::CaptureDumpComplete()
{
    render_view()->GetWebView()->mainFrame()->stopLoading();

    Send(new ShellViewHostMsg_TestFinished(routing_id()));
}

mojom::LayoutTestBluetoothFakeAdapterSetter&
BlinkTestRunner::GetBluetoothFakeAdapterSetter()
{
    if (!bluetooth_fake_adapter_setter_) {
        RenderThread::Get()->GetRemoteInterfaces()->GetInterface(
            mojo::MakeRequest(&bluetooth_fake_adapter_setter_));
    }
    return *bluetooth_fake_adapter_setter_;
}

void BlinkTestRunner::OnSetupSecondaryRenderer()
{
    DCHECK(!is_main_window_);

    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();
    interfaces->SetTestIsRunning(true);
    interfaces->ConfigureForTestWithURL(GURL(), false);
    ForceResizeRenderView(render_view(), WebSize(800, 600));
}

void BlinkTestRunner::OnReplicateTestConfiguration(
    const ShellTestConfiguration& params)
{
    test_runner::WebTestInterfaces* interfaces = LayoutTestRenderThreadObserver::GetInstance()->test_interfaces();

    test_config_ = params;

    is_main_window_ = true;
    interfaces->SetMainView(render_view()->GetWebView());

    interfaces->SetTestIsRunning(true);
    interfaces->ConfigureForTestWithURL(params.test_url,
        params.enable_pixel_dumping);
}

void BlinkTestRunner::OnSetTestConfiguration(
    const ShellTestConfiguration& params)
{
    OnReplicateTestConfiguration(params);

    ForceResizeRenderView(
        render_view(),
        WebSize(params.initial_size.width(), params.initial_size.height()));
    LayoutTestRenderThreadObserver::GetInstance()
        ->test_interfaces()
        ->TestRunner()
        ->SetFocus(render_view()->GetWebView(), true);
}

void BlinkTestRunner::OnSessionHistory(
    const std::vector<int>& routing_ids,
    const std::vector<std::vector<PageState>>& session_histories,
    const std::vector<unsigned>& current_entry_indexes)
{
    routing_ids_ = routing_ids;
    session_histories_ = session_histories;
    current_entry_indexes_ = current_entry_indexes;
    CaptureDump();
}

void BlinkTestRunner::OnReset()
{
    LayoutTestRenderThreadObserver::GetInstance()->test_interfaces()->ResetAll();
    Reset(true /* for_new_test */);
    // Navigating to about:blank will make sure that no new loads are initiated
    // by the renderer.
    WebURLRequest request = WebURLRequest(GURL(url::kAboutBlankURL));
    render_view()->GetWebView()->mainFrame()->loadRequest(request);
    Send(new ShellViewHostMsg_ResetDone(routing_id()));
}

void BlinkTestRunner::OnTestFinishedInSecondaryRenderer()
{
    DCHECK(is_main_window_ && render_view()->GetMainRenderFrame());
    TestFinished();
}

void BlinkTestRunner::OnTryLeakDetection()
{
    blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
    DCHECK_EQ(GURL(url::kAboutBlankURL), GURL(main_frame->document().url()));
    DCHECK(!main_frame->isLoading());

    leak_detector_->TryLeakDetection(main_frame);
}

void BlinkTestRunner::OnReplyBluetoothManualChooserEvents(
    const std::vector<std::string>& events)
{
    DCHECK(!get_bluetooth_events_callbacks_.empty());
    base::Callback<void(const std::vector<std::string>&)> callback = get_bluetooth_events_callbacks_.front();
    get_bluetooth_events_callbacks_.pop_front();
    callback.Run(events);
}

void BlinkTestRunner::ReportLeakDetectionResult(
    const LeakDetectionResult& report)
{
    Send(new ShellViewHostMsg_LeakDetectionDone(routing_id(), report));
}

void BlinkTestRunner::OnDestruct()
{
    delete this;
}

} // namespace content
